package com.guaoran.distributed.io.nio.two.server;

import javax.xml.bind.SchemaOutputResolver;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * @Author gucheng
 * @Description 服务端
 * 2019-04-29 17:06
 */
public class NIOServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private final int port;

    public NIOServer(int port) throws IOException {
        this.port = port;
        // 打开一个服务端的 socket 通道
        serverSocketChannel = ServerSocketChannel.open();
        // 设置非阻塞
        serverSocketChannel.configureBlocking(false);
        // 绑定端口
        // 下面是什么区别 ???
        // ??? serverSocketChannel.socket().bind()
        // ??? serverSocketChannel.bind()
        serverSocketChannel.socket().bind(new InetSocketAddress(this.port));
        // 打开一个选择器
        selector = Selector.open();
        // 注册服务通道到选择器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端已启动，绑定端口："+this.port);

        // 监听程序
        listener();
    }

    /**
     * 监听处理程序
     */
    private void listener() throws IOException {
        while (true){
            // 获得注册到选择器上的通道数量
            int selectNum = selector.select(30*1000L);
            if(selectNum==0){
                continue;
            }
            System.out.println(System.currentTimeMillis()+" -> 当前通道数量："+selectNum);

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                iterator.remove();
                // 忽略无效的 SelectionKey
                if(!key.isValid()){
                    continue;
                }
                // 处理程序
                processHandle(key);
            }
        }
    }

    /**
     * 处理程序
     * @param key
     */
    private void processHandle(SelectionKey key) throws IOException {

        // 连接就绪
        if(key.isAcceptable()){
            acceptableHandle(key);
        }
        // 读就绪
        if(key.isReadable()){
            readableHandle(key);
        }
        // 写就绪
        if(key.isWritable()){
            writableHandle(key);
        }

    }

    /**
     * 写就绪
     * @param key
     */
    private void writableHandle(SelectionKey key) throws ClosedChannelException {
        SocketChannel channel = (SocketChannel) key.channel();
        List<String> arrayList = (ArrayList<String>) key.attachment();
        for (String content : arrayList) {
            System.out.println("写入数据："+content);
            CodecUtil.write(channel,content);
        }
        arrayList.clear();
        channel.register(selector,SelectionKey.OP_READ,arrayList);
    }

    /**
     * 读就绪
     * @param key
     */
    private void readableHandle(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = CodecUtil.read(channel);
        // 处理连接中断的情况
        if(buffer == null){
            System.out.println("中断 Channel");
            channel.register(selector,0);
            return;
        }
        // 写入模式下
        if(buffer.position()>0){
            String content = CodecUtil.newStr(buffer);
            System.out.println("读到数据："+content);
            // 添加到响应队列
            List<String> arrayList = (ArrayList<String>) key.attachment();
            arrayList.add("响应："+content);
            channel.register(selector,SelectionKey.OP_WRITE,key.attachment());
        }

    }

    /**
     * 连接就绪
     * @param key
     */
    private void acceptableHandle(SelectionKey key) throws IOException {
        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
        SocketChannel accept = channel.accept();
        // 设置非阻塞
        accept.configureBlocking(false);
        // 注册可读事件到选择器上
        accept.register(selector,SelectionKey.OP_READ,new ArrayList<String>());
    }

    public static void main(String[] args) throws IOException {
        new NIOServer(8081);
    }
}
